/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright (c) 2008-2009, The KiWi Project (http://www.kiwi-project.eu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor(s):
 * 
 * 
 */
package kiwi.core.model.rdf;

import kiwi.core.model.user.KiWiUser;
import org.hibernate.Hibernate;
import org.hibernate.annotations.*;
import org.hibernate.annotations.Cache;

import javax.persistence.Entity;
import javax.persistence.*;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;

/**
 * KiWiExtendedTriples are one of the core concepts of the KiWi system. They
 * correspond to triples in RDF, but extend them with additional information
 * that is useful and/or needed in the KiWi system, for example author information,
 * version information, justifications (in case of inferred triples), etc.
 * 
 * Like a KiWiResource, each triple is associated with a single TripleStore.
 * All triples together essentially make up the TripleStore. As with KiWiResources,
 * this means that there is no 1:1 correspondence between RDF triples and KiWi triples.
 * An RDF triple may be represented by several KiWi triples in different KnowledgeSpaces.
 * <p>
 * Base/inferred flags denote the status of support of a triple:
 * <ul>
 * <li>base=true, inferred=true means  the triple is both explicit and supported by a rule</li>
 * <li>base=false, inferred=true means triple is supported by a rule but not explicit</li>
 * <li>base=true, inferred=false means triple is only explicit</li>
 * <li>base=false, inferred=false does not exist</li>
 * </ul>
 * Only triples where base=true can be deleted using TripleStore.removeTriple; triples with
 * base=false and inferred=true are managed by the reasoner.
 *
 * @author Sebastian Schaffert
 */
@SuppressWarnings({"JpaQlInspection"})
@Entity
@org.hibernate.annotations.Entity(mutable=false,optimisticLock=OptimisticLockType.VERSION)
@Immutable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@BatchSize(size = 100)
@Table(
	name = "TRIPLES"
//	uniqueConstraints = {@UniqueConstraint(columnNames={"subject_id", "property_id", "object_id", "context_id"})}
)
@org.hibernate.annotations.Table(
	appliesTo = "TRIPLES",
	indexes = { 
		@Index(name = "idx_triples_spoc", columnNames = {"subject_id","property_id","object_id","context_id"}),
		@Index(name = "idx_triples_spo", columnNames = {"subject_id","property_id","object_id"}),
		@Index(name = "idx_triples_sp", columnNames = {"subject_id","property_id"}),
		//@Injectdex(name = "idx_triples_so", columnNames = {"subject_id","object_id"}),
		@Index(name = "idx_triples_po", columnNames = {"property_id","object_id"})
	}
)
@NamedQueries({
        // the different queries for the listTriples method in TripleStore
        @NamedQuery(name  = "tripleStore.listTriplesBy",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.deleted = false"),
         @NamedQuery(name  = "tripleStore.listTriplesByS",
                    query = "from KiWiTriple t " +
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.subject = :subject" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByP",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.property = :property" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByO",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.context "+
                            "where t.object = :object" +
                            "  and (t.deleted = false)"),
         @NamedQuery(name  = "tripleStore.listTriplesByC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "where t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesBySP",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.context "+
                            "join fetch t.object "+
                            "where t.subject = :subject" +
                            "  and t.property = :property" +
                            "  and (t.deleted = false)"),
         @NamedQuery(name  = "tripleStore.listTriplesBySO",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.subject = :subject" +
                            "  and t.object = :object" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesBySC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.subject = :subject" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByPO",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.property = :property" +
                            "  and t.object = :object" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByPC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.property = :property" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByOC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.object = :object" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesBySPO",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.subject = :subject" +
                            "  and t.property = :property" +
                            "  and t.object = :object" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesBySPC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.subject = :subject" +
                            "  and t.property = :property" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesByPOC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.object = :object" +
                            "  and t.property = :property" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),
        @NamedQuery(name  = "tripleStore.listTriplesBySPOC",
                    query = "from KiWiTriple t " +
                            "join fetch t.subject "+
                            "join fetch t.property "+
                            "join fetch t.object "+
                            "join fetch t.context "+
                            "where t.object = :object" +
                            "  and t.property = :property" +
                            "  and t.subject = :subject" +
                            "  and t.context = :context" +
                            "  and (t.deleted = false)"),


        @NamedQuery(name  = "tripleStore.deleteTriple",
                query = "update KiWiTriple t " +
                        "set t.deleted = true "+
                        "where t.id = :id"),
        @NamedQuery(name  = "tripleStore.reallyDeleteTriple",
                query = "delete KiWiTriple t " +
                        "where t.id = :id"),
        @NamedQuery(name  = "tripleStore.deleteTriples",
                query = "update KiWiTriple t " +
                        "set t.deleted = true "+
                        "where t.id in (:ids)"),
        @NamedQuery(name  = "tripleStore.undeleteTriple",
                query = "update KiWiTriple t " +
                        "set t.deleted = false "+
                        "where t.id = :id"),
        @NamedQuery(name  = "tripleStore.undeleteTriples",
                query = "update KiWiTriple t " +
                        "set t.deleted = false "+
                        "where t.id in (:ids)"),
        @NamedQuery(name  = "tripleStore.triplesByIds",
                query = "from KiWiTriple t " +
                        "where t.id in (:ids)")

})
public class KiWiTriple  implements Serializable {
    
	/**
	 * 
	 */
	private static final long serialVersionUID = -8726615974625660845L;
	
	@Id
	@NotNull
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Long id;
	

	@NotNull
	@Immutable
	@ManyToOne(fetch = FetchType.EAGER, optional=false)	
	@Index(name="idx_triple_subject")
	@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
    private KiWiResource    subject;
    
	@NotNull
	@Immutable
	@ManyToOne(fetch = FetchType.EAGER, optional=false)
	@Index(name="idx_triple_property")
	@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
    private KiWiUriResource property;
	
	@NotNull
	@Immutable
	@ManyToOne(fetch = FetchType.EAGER, optional=false)
	@Index(name="idx_triple_object")
	@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
    private KiWiNode        object;

    @NotNull
	@Immutable
	@ManyToOne(fetch = FetchType.EAGER, optional=true)
	@Index(name="idx_triple_context")
    private KiWiUriResource context;
    
	@Immutable
	@ManyToOne(fetch = FetchType.LAZY, optional=true)
    private KiWiUser author;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date created;
    
	private Boolean deleted;

	@Transient
	private Boolean newTriple;
	

	@Version
	private Long version;
	
	
	public KiWiTriple() {
		this.created = new Date();
		this.deleted = false;
		this.newTriple = true;
	}
	


	public KiWiTriple(KiWiResource subject, KiWiUriResource property, KiWiNode object, KiWiUriResource context) {
		this();
		this.subject = subject;
		this.property = property;
		this.object   = object;
        this.context  = context;

        assert(subject  != null);
        assert(property != null);
        assert(object   != null);
        assert(context  != null);
	}

   /**
     * Get the object of this extended triple.
     * @return
     */
    public KiWiNode getObject() {
        return object;
    }

    /**
     * Set the object of this extended triple to the given KiWiNode (either a resource or a literal)
     * @param object
     */
    public void setObject(KiWiNode object) {
        this.object = object;
    }

    /**
     * Get the property of this extended triple. Always a KiWiUriResource.
     * @return
     */
    public KiWiUriResource getProperty() {
        return property;
    }

    /**
     * Set the property of this extended triple. Always needs to be a KiWiUriResource
     * @param property
     */
    public void setProperty(KiWiUriResource property) {
        this.property = property;
    }

    /**
     * Get the subject of this extended triple. Always a resource.
     * @return
     */
    public KiWiResource getSubject() {
        return subject;
    }

    /**
     * Set the subject of this extended triple to the provided KiWiResource
     * @param subject
     */
    public void setSubject(KiWiResource subject) {
        this.subject = subject;
    }

    /**
     * Get the unique triple identifier of this extended triple. Returns a KiWiUriResource identifying this triple.
     * @return
     */
    public KiWiUriResource getContext() {
        return context;
    }

    /**
     * Set the unique triple identifier of this extended triple to the provided KiWiUriResource. The caller needs
     * to ensure that the tripleId is unique over the KiWi system; otherwise, the system might not function correctly.
     * @param context
     */
    public void setContext(KiWiUriResource context) {
        this.context = context;
    }

    
    /**
     * Return the author of this extended triple.
     * 
     * Internally, this is determined using the tripleId of the extended triple and looking it up in the 
     * database.
     * 
     * @return
     */
    public KiWiUser getAuthor() {
        return author;
    }
    
    /**
     * Set the author of this extended triple.
     * 
     * Changes will be persisted as part of the database using the tripleId as unique identifier.
     * 
     * @param author
     */
    public void setAuthor(KiWiUser author) {
        this.author = author;
    }

    
    
	/**
	 * @return the created
	 */
	public Date getCreated() {
		return created;
	}

	/**
	 * @param created the created to set
	 */
	public void setCreated(Date created) {
		this.created = created;
	}

	/**
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	
	
	/**
	 * @return the deleted
	 */
	public Boolean isDeleted() {
		return deleted;
	}


	/**
	 * @param deleted the deleted to set
	 */
	public void setDeleted(Boolean deleted) {
		this.deleted = deleted;
	}



	/**
	 * TODO: please comment why that method is necessary
	 * 
	 */
	public boolean isNew() {
		return newTriple;
	}
	
	/**
	 * TODO: please comment why that method is necessary
	 * @param isNew
	 */
	public void setNew(boolean isNew) {
		newTriple = isNew;
	}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
//        if (!(o instanceof KiWiTriple)) return false;

        if (Hibernate.getClass(this) != Hibernate.getClass(o)) {
            return false;
        }

        KiWiTriple triple = (KiWiTriple) o;

        if (!getContext().equals(triple.getContext())) return false;
        if (!getObject().equals(triple.getObject())) return false;
        if (!getProperty().equals(triple.getProperty())) return false;
        if (!getSubject().equals(triple.getSubject())) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = getSubject().hashCode();
        result = 31 * result + getProperty().hashCode();
        result = 31 * result + getObject().hashCode();
        result = 31 * result + getContext().hashCode();
        return result;
    }

    public String toN3() {
		return "<"+subject.toString()+"> <" + property.toString() + "> <" + object.toString() + ">";
	}

    public String toTurtle() {
        return subject.toTurtle() + " " + property.toTurtle() + " " + object.toTurtle() + " .\n";
    }


    public String toJSON() {
        return "{ " + subject.toJSON(false) + " : { " + property.toJSON(false) + " : [" + object.toJSON(true) + "] } }";
    }


    @Override
    public String toString() {
    	if(context != null) {
    		return "{"+subject.toString()+" "+property.toString()+" "+object.toString()+"}@"+context.toString();
    	} else {
    		return "{"+subject.toString()+" "+property.toString()+" "+object.toString()+"}@GLOBAL";   		
    	}
    }
    
    /**
     * Return a unique key to be used in caches and similar.
     * 
     * @return
     */
    public String getKey() {
    	return toString();
    }
    

    /**
	 * @return the version
	 */
	public Long getVersion() {
		return version;
	}


	/**
	 * @param version the version to set
	 */
	public void setVersion(Long version) {
		this.version = version;
	}


	@PostLoad
	public void postLoad() {
		this.newTriple = false;
	}
}
